﻿package hapi.ui;

import flash.MovieClip;
import hapi.ui.FlashButton;
import hapi.ui.Interactive;
import hapi.ui.Slider;
import hapi.event.Notifier;
import hapi.event.Dispatcher;

class FlashSlider implements Slider, implements Interactive {

	public var onChange(default, null):Notifier<SliderChangeEvent>;
	var onChangeDispatcher:Dispatcher<SliderChangeEvent>;
	
	var handle:FlashButton;
	var slide:FlashButton;
	var skin:FlashSliderSkin;
	public var valueMin:Float;
	public var valueMax:Float;
	public var currentValue(default,null):Float;
	var orientation:SliderOrientation;
	var timer:haxe.Timer;
	
	public function new(skin) {
		initSkin(skin);
		initNotifiers();
		initProperties();
		initHandlers();
	}
	
	public function change(value:Float):Float {
		if(isEnabled()){
			var newValue = projectValue(value);
			if (newValue != currentValue) {
				var event:SliderChangeEvent = createEvent(currentValue, newValue);
				currentValue = newValue;
				refreshHandlePositionByValue(currentValue);
				onChangeDispatcher.dispatchEvent( event );
			}
			return newValue;
		}else
			return currentValue;
	}
	
	public function increase(delta:Float):Float {
		return change(currentValue + delta);
	}
	
	public function isEnabled() {
		return handle.isEnabled();
	}
	
	public function enable() {
		handle.enable();
		slide.enable();
	}
	
	public function disable() {
		handle.disable();
		slide.disable();
	}
	
	private function initNotifiers() {
		onChange = cast (onChangeDispatcher = new hapi.event.HapiDispatcher());
	}
	
	private function initSkin(mc) {
		this.skin = mc;
		handle = new FlashButton(skin.handle);
		slide = new FlashButton(skin.slide);
	}
	
	private function initProperties() {
		this.valueMin = 0.;
		this.valueMax = 1.;
		this.currentValue = valueMin;
		this.orientation = skin.slide._width > skin.slide._height ? horizontal : vertical;
	}
	
	private function initHandlers() {
		handle.onPress.call( startDrag );
		handle.onRelease.call( stopDrag );
		slide.onPress.call( jumpToCursor );
	}
	
	private function jumpToCursor(event) {
		refreshHandlePositionByValue(getValueByPosition({x: skin.slide._xmouse, y:skin.slide._ymouse}));
		refreshValueByPosition();
	}
	
	private function startDrag(event) {
		var range = getHandlePositionRage();
		skin.handle.startDrag(false, range.min.x, range.min.y, range.max.x, range.max.y);
		timer = new haxe.Timer(10);
		timer.run = refreshValueByPosition;
	}
	
	private function stopDrag(event) {
		skin.handle.stopDrag();
		timer.stop();
		timer = null;
	}
	
	private function refreshValueByPosition() {
		change(getValueByPosition({x: skin.handle._x, y:skin.handle._y}));
	}
	
	private function refreshHandlePositionByValue(value) {
		if ( timer == null ) {
			var p = getPositionByValue(value);
			skin.handle._x = p.x;
			skin.handle._y = p.y;
		}
	}
	
	private inline function getHandlePositionRage() {
		var range = { min: { x:0., y:0. }, max: { x:0., y:0. } };
		if ( orientation == horizontal ) {
			range.max.x = skin.slide._width - skin.handle._width;
		}else {
			range.max.y = skin.slide._height - skin.handle._height;
		}
		return range;
	}
	
	private function getValueByPosition( position: { var x:Float; var y:Float; } ) {
		var range = getHandlePositionRage();
		var coef:Float;
		if ( orientation == horizontal ) {
			coef = position.x / range.max.x;
		}else {
			coef = position.y / range.max.y;
		}
		return this.coefToValue(coef);
	}
	
	private function getPositionByValue(value:Float) {
		var range = getHandlePositionRage();
		var pos = valueToCoef(value);
		if ( orientation == horizontal ) {
			return {
				x: pos * range.max.x,
				y: skin.handle._y
			}
		}else {
			return {
				x: skin.handle._x,
				y: pos * range.max.y
			}
		}
	}
	
	private function createEvent(before:Float, after:Float):SliderChangeEvent {
		return { beforeChange: before, afterChange: after, source: cast this }
	}
	
	private function coefToValue(coef:Float):Float {
		return valueMin + (valueMax - valueMin) * coef;
	}
	
	private function valueToCoef(value:Float):Float {
		return (value - valueMin) / (valueMax - valueMin);
	}
	
	private function projectValue(value:Float):Float {
		if( valueMax >= valueMin )
			return Math.min(valueMax, Math.max(value, valueMin ));
		else
			return Math.min(valueMin, Math.max(value, valueMax ));
	}
}

private enum SliderOrientation {
	horizontal;
	vertical;
}

typedef FlashSliderSkin = { > MovieClip,
	var handle:FlashButtonSkin;
	var slide:FlashButtonSkin;
}